/*********************************************
 * Student    : Teodoro Cipresso
 * Course     : CS286
 * Professor  : Vidya Setlur
 * Assignment : Term Project (BlueTanks)
 *********************************************/
package edu.sjsu.cs286.teodoro.cipresso.playfield;

import java.io.IOException;

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.game.LayerManager;
import javax.microedition.lcdui.game.Sprite;
import javax.microedition.lcdui.game.TiledLayer;

import edu.sjsu.cs286.teodoro.cipresso.bluetooth.BlueTankMessageHandler;
import edu.sjsu.cs286.teodoro.cipresso.sprites.BlueTankImageHolder;
import edu.sjsu.cs286.teodoro.cipresso.sprites.BlueTankVehicleSprite;
import edu.sjsu.cs286.teodoro.cipresso.sprites.IBlueTankSprite;
import edu.sjsu.cs286.teodoro.cipresso.sprites.IBlueTankSpriteVectorTraversal;
import edu.sjsu.cs286.teodoro.cipresso.wizard.BlueTankGameContainer;

/**
 * This class represents the graphical user interface to the game. The playfield
 * consists of multiple tiled layers and sprites whose state is updated from the
 * input of both players in the game.
 * 
 * @author Teodoro Cipresso
 */
public class BlueTankGameCanvas extends GameCanvas implements Runnable,
		IBlueTankCanvas, IBlueTankSpriteVectorTraversal {
	private boolean playing;

	private TiledLayer passableTerrain;

	private TiledLayer impassableTerrain;

	private LayerManager layerManager;

	private Command exitCommand = new Command("Exit", Command.EXIT, 0);

	private int playfieldX, playfieldY;

	private int playFieldEdgeWidth;

	private BlueTankGameContainer container;

	private BlueTankMessageHandler messageHandler;

	private BlueTankVehicleSprite selfTank = null;

	private BlueTankVehicleSprite peerTank = null;

	private boolean gameWon = false;

	private boolean gameLost = false;

	private boolean gameDraw = false;

	/**
	 * Default constructor. The main two layers for the playfield are created
	 * here. One layer represents the impassable parts of the playfield and the
	 * other the traversable part.
	 * 
	 * @param parent,
	 *            the containing midlet
	 */
	public BlueTankGameCanvas(BlueTankGameContainer parent) {
		super(true);
		container = parent;
		//		
		try {
			//
			playFieldEdgeWidth = (getWidth() - PLAYFIELD_WIDTH) / 2;
			//
			passableTerrain = BlueTankTerrain.createPassableTerrain();
			impassableTerrain = BlueTankTerrain.createImpassableTerrain();
			layerManager = new LayerManager();
			layerManager.append(passableTerrain);
			layerManager.append(impassableTerrain);
		} catch (IOException e) {
			e.printStackTrace();
		}
		//
		addCommand(exitCommand);
		setCommandListener(parent.getMidlet());
	} // BlueTankGameCanvas()

	/**
	 * Start the run method of the game canvas on a new thread.
	 */
	public void start() {
		playing = true;
		new Thread(this).start();
		placeTanksOnPlayfield();
	} // start()

	/**
	 * Simply stop the game loop
	 */
	public void stop() {
		playing = false;
	} // stop()

	/**
	 * This method implements the main game loop. The game canvas cannot be
	 * "busy" updated so we define a reasonable delay for updating (repainting)
	 * all the layers and/or graphics. Each update cycle checks what buttons the
	 * user has pressed.
	 */
	public void run() {
		// game loop while playing
		while (playing) {
			// current time
			long start = System.currentTimeMillis();
			// check user keys pressed
			checkGameKeysPressed();
			// repaint game canvas
			repaintGameScreen(getGraphics());
			// compute the time after we painted the canvas
			long end = System.currentTimeMillis();
			int duration = (int) (end - start);
			// if painting didn't take as long as our fixed cycle delay, wait on
			// the difference
			if (duration < GAME_CYCLE_DELAY) {
				try {
					Thread.sleep(GAME_CYCLE_DELAY - duration);
				} catch (InterruptedException ie) {
					stop(); // if we can't sleep, something is terribly wrong,
					// stop the game loop
				} // try
			} // if
		} // while
	} // run()

	/**
	 * This method checks the state of the keys (state is changed when user
	 * presses a key). We're interested in only a small set of keys.
	 */
	private void checkGameKeysPressed() {
		// key pressed events only affect self
		int keyStates = getKeyStates();
		// spin tank clockwise
		if ((keyStates & LEFT_PRESSED) != 0)
			selfTank.tryRotate(-1);
		// spin tank counter clock-wise
		else if ((keyStates & RIGHT_PRESSED) != 0)
			selfTank.tryRotate(1);
		// drive tank forward
		else if ((keyStates & UP_PRESSED) != 0)
			selfTank.tryDrive(IBlueTankSprite.TANK_SPEED);
		// drive tank backward
		else if ((keyStates & DOWN_PRESSED) != 0)
			selfTank.tryDrive(-IBlueTankSprite.TANK_SPEED);
		// fire the tank cannon
		else if ((keyStates & FIRE_PRESSED) != 0) {
			selfTank.tryFireCannon();
		}
		// give tank sprites a chance to update their state
		selfTank.update();
		peerTank.update();
	} // checkGameKeysPressed()

	/**
	 * Repaint the game canvas and any other graphics painted on the outside of
	 * it. The background is also painted here. It's been performing poorly,
	 * especially on the mobile device, maybe I should get rid of it.
	 * 
	 * @param graphics,
	 *            handle on the graphics object
	 */
	private void repaintGameScreen(Graphics graphics) {
		// draw camouflage background
		graphics.drawImage(BlueTankImageHolder.getCamouflage(getWidth(),
				getHeight()), 0, 0, 0);
		// scope layer repaint to just playfield
		playfieldX = (getWidth() - PLAYFIELD_WIDTH) / 2;
		playfieldY = (getHeight() - PLAYFIELD_HEIGHT) / 2;
		layerManager.paint(graphics, playfieldX, playfieldY);
		// paint armor graphs on either side of the playfield
		paintArmorGraph(graphics, true, selfTank);
		paintArmorGraph(graphics, false, peerTank);
		// check if either play has won
		paintGameWonLostDraw(graphics);
		// force any pending repaints
		flushGraphics();
	} // repaintGameScreen()

	/**
	 * This method paints graphs on either side of the playfield that shows the
	 * remaining armor points for each of the players. This is the indication of
	 * which player is winning the game.
	 * 
	 * @param graphics,
	 *            handle on the graphics object
	 * @param left,
	 *            whether the graph is on the left side of the playfield (this
	 *            player)
	 * @param tank,
	 *            the tank object to draw the graph for
	 */
	public void paintArmorGraph(Graphics graphics, boolean left,
			BlueTankVehicleSprite tank) {
		// compute base X coordinate of graph
		int bX;
		if (left) {
			bX = playFieldEdgeWidth - 24;
		} else {
			bX = (getWidth() - playFieldEdgeWidth) + 8;
		}
		Double tH = new Double((tank.getHeight() * 1.25));
		// compute maximum height of graph
		Double mH = new Double(PLAYFIELD_HEIGHT - tH.doubleValue());
		// compute maximum Y coordinate of graph
		int mY = playfieldY + mH.intValue();
		// compute height of graph based on remaining armor points
		int h = new Double((mH.intValue() / IBlueTankSprite.MAX_ARMOR_POINTS)
				* tank.getArmorPoints()).intValue();
		int y = mY - h;
		// Set color of graph to match ownerTank sprite and draw it
		if (tank.getArmorPoints() > 0) {
			// draw bar graph
			graphics.setColor(0xFFFFFF);
			graphics.drawRect(bX - 1, y - 1, ARMOR_BAR_WIDTH + 1, h + 1); // outline
			graphics.setColor(tank.getTankRGB());
			graphics.fillRect(bX, y, ARMOR_BAR_WIDTH, h);
		} // if armor points lefts
		// Draw graphic player hint below graph
		Image hint = null;
		if (tank.getArmorPoints() > 0) {
			// active tank hint
			hint = BlueTankImageHolder.getTankSelectImageByName(tank
					.getTankName());
		} else {
			// destroyed tank hint
			hint = Image.createImage(BlueTankImageHolder.getDestroyedTankOne(),
					0, 0, 32, 32, Sprite.TRANS_NONE);
		}
		y = (playfieldY + PLAYFIELD_HEIGHT) - tank.getHeight();
		graphics.drawImage(hint, bX + (tank.getWidth() / 4), y, Graphics.TOP
				| Graphics.HCENTER);
	} // paintArmorGraph()

	/**
	 * This method checks if the game is won or lost and displays an appropriate
	 * message and stops the game
	 */
	private void paintGameWonLostDraw(Graphics graphics) {
		if (gameWon || gameLost || gameDraw) {
			// select game result image to draw
			Image image = null;
			if (gameWon) {
				image = BlueTankImageHolder.getBattleWon();
			} else if (gameLost) {
				image = BlueTankImageHolder.getBattleLost();
			} else if (gameDraw) {
				image = BlueTankImageHolder.getBattleDraw();
			}
			//
			int x = (getWidth() / 2) - (image.getWidth() / 2);
			int y = (getHeight() / 2) - (image.getHeight() / 2);
			graphics.drawImage(image, x, y, Graphics.TOP | Graphics.LEFT);
			//
		} else if (isSelfWon() && !isPeerWon()) {
			// tell peer we think we won
			if (messageHandler == null) {
				messageHandler = container.getBtManager().getBtMessageHandler();
				// delay for state to settle
				try {
					Thread.sleep(BlueTankMessageHandler.COMMIT_DELAY);
					messageHandler.sendGameWinCommitRequest();
				} catch (InterruptedException e) {
				}
			} //
		}
		//
	} // checkGameWon()

	/**
	 * This method is called when this player selects their tank (not the peer).
	 * The tank sprite is created and placed on the playfield. The starting
	 * location is determined by who is hosting the game.
	 * 
	 * @param image,
	 *            framed tank image (color specific).
	 * 
	 * @param rgb,
	 *            the actual color of the body of the tank (used for painting
	 *            armor graph).
	 * @param name,
	 *            display name of the selected tank
	 */
	public void setSelfTank(Image image, int rgb, String name) {
		try {
			// prevent duplicates on board
			if (selfTank != null) {
				layerManager.remove(selfTank);
			}
			// create ownerTank sprite
			selfTank = BlueTankVehicleSprite.createTank(container, image, rgb,
					name);
		} catch (IOException e) {
			e.printStackTrace();
		}
	} // setSelfTank()

	/**
	 * This method is called when this player selects their tank (not the peer).
	 * The tank sprite is created and placed on the playfield. The starting
	 * location is determined by who is hosting the game.
	 * 
	 * @param image,
	 *            framed tank image (color specific).
	 * 
	 * @param rgb,
	 *            the actual color of the body of the tank (used for painting
	 *            armor graph).
	 * @param name,
	 *            display name of the selected tank
	 */
	public void setPeerTank(Image image, int rgb, String name) {
		try {
			// prevent more than one peer ownerTank
			if (peerTank != null) {
				layerManager.remove(peerTank);
			}
			// create ownerTank sprite
			peerTank = BlueTankVehicleSprite.createTank(container, image, rgb,
					name);
		} catch (IOException e) {
			e.printStackTrace();
		}
	} // setPeerTank()

	/**
	 * This method positions the tank sprites on the playfield. The tank hosting
	 * the game is placed on the upper left hand corner of the playfield while
	 * the client is placed on the bottom right corner.
	 */
	private void placeTanksOnPlayfield() {
		// compute possible positions (2)
		int xT = playfieldX + selfTank.getWidth();
		int yT = playfieldY + selfTank.getHeight();
		int xB = (playfieldX + PLAYFIELD_WIDTH) - (selfTank.getWidth() * 2);
		int yB = (playfieldY + PLAYFIELD_HEIGHT) - (selfTank.getHeight() * 2);
		// position of tanks is based on who's hosting the game
		if (container.getBtManager().isServer()) {
			// server tank goes upper left
			selfTank.setPosition(xT, yT);
			selfTank.setCannonDirection(FACE_DOWN);
			// other tank goes lower right
			peerTank.setPosition(xB, yB);
			peerTank.setCannonDirection(FACE_UP);
		} else {
			// server tank goes upper left
			peerTank.setPosition(xT, yT);
			peerTank.setCannonDirection(FACE_DOWN);
			// other tank goes lower right
			selfTank.setPosition(xB, yB);
			selfTank.setCannonDirection(FACE_UP);
		} //
		selfTank.doRotate(0, System.currentTimeMillis());
		peerTank.doRotate(0, System.currentTimeMillis());
		layerManager.insert(selfTank, 0);
		layerManager.insert(peerTank, 0);
	} // positionTankOnPlayfield()

	/**
	 * Getter for TiledLayer
	 */
	public TiledLayer getImpassableTerrain() {
		return impassableTerrain;
	} // getImpassableTerrain()

	/**
	 * Getter for TiledLayer
	 */
	public TiledLayer getPassableTerrain() {
		return passableTerrain;
	} // getPassableTerrain()

	/**
	 * Getter for this tank sprite
	 */
	public BlueTankVehicleSprite getSelfTank() {
		return selfTank;
	} // getSelfTank()

	/**
	 * Getter for peer tank sprite
	 */
	public BlueTankVehicleSprite getPeerTank() {
		return peerTank;
	} // getPeerTank()

	/**
	 * Getter for the layer manager
	 */
	public LayerManager getLayerManager() {
		return layerManager;
	} // getLayerManager()

	/**
	 * Getter for whether the self points are zero
	 */
	public boolean isPeerWon() {
		return (selfTank.getArmorPoints() == 0);
	} // isPeerWon()

	/**
	 * Getter for whether the peer points are zero
	 */
	public boolean isSelfWon() {
		return (peerTank.getArmorPoints() == 0);
	} // isSelfWon()

	/**
	 * Setter for game draw
	 */
	public void setGameDraw(boolean gameDraw) {
		this.gameDraw = gameDraw;
	}

	/**
	 * setter for game won
	 */
	public void setGameWon(boolean gameWon) {
		this.gameWon = gameWon;
	}

	/**
	 * setter for game lost
	 */
	public void setGameLost(boolean gameLost) {
		this.gameLost = gameLost;
	} // setGameLost()

} // BlueTankGameCanvas()

